/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.ui;

import java.util.ArrayList;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Handler;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ShareActionProvider;
import android.widget.ShareActionProvider.OnShareTargetSelectedListener;

import com.android.gallery3d.R;
import com.android.gallery3d.app.AbstractGalleryActivity;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.ui.MenuExecutor.ProgressListener;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;

public class ActionModeHandler implements Callback,
		PopupList.OnPopupItemClickListener {

	private static final String TAG = ActionModeHandler.class.getSimpleName();

	private static final int MAX_SELECTED_ITEMS_FOR_SHARE_INTENT = 300;
	private static final int MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT = 10;

	private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE
			| MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE
			| MediaObject.SUPPORT_CACHE;

	public interface ActionModeListener {
		public boolean onActionItemClicked(MenuItem item);
	}

	private final AbstractGalleryActivity mActivity;
	private final MenuExecutor mMenuExecutor;
	private final SelectionManager mSelectionManager;
	private final NfcAdapter mNfcAdapter;
	private Menu mMenu;
	private MenuItem mSharePanoramaMenuItem;
	private MenuItem mShareMenuItem;
	private ShareActionProvider mSharePanoramaActionProvider;
	private ShareActionProvider mShareActionProvider;
	private SelectionMenu mSelectionMenu;
	private ActionModeListener mListener;
	private Future<?> mMenuTask;
	private final Handler mMainHandler;
	private ActionMode mActionMode;

	private static class GetAllPanoramaSupports implements
			PanoramaSupportCallback {
		private int mNumInfoRequired;
		private JobContext mJobContext;
		public boolean mAllPanoramas = true;
		public boolean mAllPanorama360 = true;
		public boolean mHasPanorama360 = false;
		private Object mLock = new Object();

		public GetAllPanoramaSupports(ArrayList<MediaObject> mediaObjects,
				JobContext jc) {
			mJobContext = jc;
			mNumInfoRequired = mediaObjects.size();
			for (MediaObject mediaObject : mediaObjects) {
				mediaObject.getPanoramaSupport(this);
			}
		}

		@Override
		public void panoramaInfoAvailable(MediaObject mediaObject,
				boolean isPanorama, boolean isPanorama360) {
			synchronized (mLock) {
				mNumInfoRequired--;
				mAllPanoramas = isPanorama && mAllPanoramas;
				mAllPanorama360 = isPanorama360 && mAllPanorama360;
				mHasPanorama360 = mHasPanorama360 || isPanorama360;
				if (mNumInfoRequired == 0 || mJobContext.isCancelled()) {
					mLock.notifyAll();
				}
			}
		}

		public void waitForPanoramaSupport() {
			synchronized (mLock) {
				while (mNumInfoRequired != 0 && !mJobContext.isCancelled()) {
					try {
						mLock.wait();
					} catch (InterruptedException e) {
						// May be a cancelled job context
					}
				}
			}
		}
	}

	public ActionModeHandler(AbstractGalleryActivity activity,
			SelectionManager selectionManager) {
		Log.d(TAG, "ActionModeHandler");
		mActivity = Utils.checkNotNull(activity);
		mSelectionManager = Utils.checkNotNull(selectionManager);
		mMenuExecutor = new MenuExecutor(activity, selectionManager);
		mMainHandler = new Handler(activity.getMainLooper());
		mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity
				.getAndroidContext());
	}

	public void startActionMode() {
		Log.d(TAG, "startActionMode");
		Activity a = mActivity;
		mActionMode = a.startActionMode(this);
		View customView = LayoutInflater.from(a).inflate(R.layout.action_mode,
				null);
		mActionMode.setCustomView(customView);
		mSelectionMenu = new SelectionMenu(a,
				(Button) customView.findViewById(R.id.selection_menu), this);
		updateSelectionMenu();
	}

	public void finishActionMode() {
		Log.d(TAG, "finishActionMode");
		mActionMode.finish();
	}

	public void setTitle(String title) {
		mSelectionMenu.setTitle(title);
	}

	public void setActionModeListener(ActionModeListener listener) {
		mListener = listener;
	}

	private WakeLockHoldingProgressListener mDeleteProgressListener;

	@Override
	public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
		Log.d(TAG, "onActionItemClicked -- menuItem: " + item);
		GLRoot root = mActivity.getGLRoot();
		root.lockRenderThread();
		try {
			boolean result;
			// Give listener a chance to process this command before it's routed
			// to
			// ActionModeHandler, which handles command only based on the action
			// id.
			// Sometimes the listener may have more background information to
			// handle
			// an action command.
			if (mListener != null) {
				result = mListener.onActionItemClicked(item);
				if (result) {
					mSelectionManager.leaveSelectionMode();
					return result;
				}
			}
			ProgressListener listener = null;
			String confirmMsg = null;
			int action = item.getItemId();
			if (action == R.id.action_delete) {
				confirmMsg = mActivity.getResources().getQuantityString(
						R.plurals.delete_selection,
						mSelectionManager.getSelectedCount());
				if (mDeleteProgressListener == null) {
					mDeleteProgressListener = new WakeLockHoldingProgressListener(
							mActivity, "Gallery Delete Progress Listener");
				}
				listener = mDeleteProgressListener;
			}
			mMenuExecutor.onMenuClicked(item, confirmMsg, listener);
		} finally {
			root.unlockRenderThread();
		}
		return true;
	}

	@Override
	public boolean onPopupItemClick(int itemId) {
		GLRoot root = mActivity.getGLRoot();
		root.lockRenderThread();
		try {
			if (itemId == R.id.action_select_all) {
				updateSupportedOperation();
				mMenuExecutor.onMenuClicked(itemId, null, false, true);
			}
			return true;
		} finally {
			root.unlockRenderThread();
		}
	}

	private void updateSelectionMenu() {
		// update title
		int count = mSelectionManager.getSelectedCount();
		String format = mActivity.getResources().getQuantityString(
				R.plurals.number_of_items_selected, count);
		setTitle(String.format(format, count));

		// For clients who call SelectionManager.selectAll() directly, we need
		// to ensure the
		// menu status is consistent with selection manager.
		mSelectionMenu.updateSelectAllMode(mSelectionManager.inSelectAllMode());
	}

	private final OnShareTargetSelectedListener mShareTargetSelectedListener = new OnShareTargetSelectedListener() {
		@Override
		public boolean onShareTargetSelected(ShareActionProvider source,
				Intent intent) {
			mSelectionManager.leaveSelectionMode();
			return false;
		}
	};

	@Override
	public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
		return false;
	}

	@Override
	public boolean onCreateActionMode(ActionMode mode, Menu menu) {
		Log.d(TAG, "onCreateActionMode");
		mode.getMenuInflater().inflate(R.menu.operation, menu);

		mMenu = menu;
		mSharePanoramaMenuItem = menu.findItem(R.id.action_share_panorama);
		if (mSharePanoramaMenuItem != null) {
			mSharePanoramaActionProvider = (ShareActionProvider) mSharePanoramaMenuItem
					.getActionProvider();
			mSharePanoramaActionProvider
					.setOnShareTargetSelectedListener(mShareTargetSelectedListener);
			mSharePanoramaActionProvider
					.setShareHistoryFileName("panorama_share_history.xml");
		}
		mShareMenuItem = menu.findItem(R.id.action_share);
		if (mShareMenuItem != null) {
			mShareActionProvider = (ShareActionProvider) mShareMenuItem
					.getActionProvider();
			mShareActionProvider
					.setOnShareTargetSelectedListener(mShareTargetSelectedListener);
			mShareActionProvider.setShareHistoryFileName("share_history.xml");
		}
		return true;
	}

	@Override
	public void onDestroyActionMode(ActionMode mode) {
		Log.d(TAG, "onCreateActionMode");
		mSelectionManager.leaveSelectionMode();
	}

	private ArrayList<MediaObject> getSelectedMediaObjects(JobContext jc) {
		Log.d(TAG, "getSelectedMediaObjects");
		ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false);
		if (unexpandedPaths.isEmpty()) {
			// This happens when starting selection mode from overflow menu
			// (instead of long press a media object)
			return null;
		}
		ArrayList<MediaObject> selected = new ArrayList<MediaObject>();
		DataManager manager = mActivity.getDataManager();
		for (Path path : unexpandedPaths) {
			if (jc.isCancelled()) {
				return null;
			}
			selected.add(manager.getMediaObject(path));
		}
		return selected;
	}

	// Menu options are determined by selection set itself.
	// We cannot expand it because MenuExecuter executes it based on
	// the selection set instead of the expanded result.
	// e.g. LocalImage can be rotated but collections of them (LocalAlbum)
	// can't.
	private int computeMenuOptions(ArrayList<MediaObject> selected) {
		Log.d(TAG, "getSelectedMediaObjects");
		int operation = MediaObject.SUPPORT_ALL;
		int type = 0;
		for (MediaObject mediaObject : selected) {
			int support = mediaObject.getSupportedOperations();
			type |= mediaObject.getMediaType();
			operation &= support;
		}

		switch (selected.size()) {
		case 1:
			final String mimeType = MenuExecutor.getMimeType(type);
			if (!GalleryUtils.isEditorAvailable(mActivity, mimeType)) {
				operation &= ~MediaObject.SUPPORT_EDIT;
			}
			break;
		default:
			operation &= SUPPORT_MULTIPLE_MASK;
		}
		return operation;
	}

	@TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
	private void setNfcBeamPushUris(Uri[] uris) {
		if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
			mNfcAdapter.setBeamPushUrisCallback(null, mActivity);
			mNfcAdapter.setBeamPushUris(uris, mActivity);
		}
	}

	// Share intent needs to expand the selection set so we can get URI of
	// each media item
	private Intent computePanoramaSharingIntent(JobContext jc, int maxItems) {
		ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true,
				maxItems);
		if (expandedPaths == null || expandedPaths.size() == 0) {
			return new Intent();
		}
		final ArrayList<Uri> uris = new ArrayList<Uri>();
		DataManager manager = mActivity.getDataManager();
		final Intent intent = new Intent();
		for (Path path : expandedPaths) {
			if (jc.isCancelled())
				return null;
			uris.add(manager.getContentUri(path));
		}

		final int size = uris.size();
		if (size > 0) {
			if (size > 1) {
				intent.setAction(Intent.ACTION_SEND_MULTIPLE);
				intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
				intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
			} else {
				intent.setAction(Intent.ACTION_SEND);
				intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
				intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
			}
			intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
		}
		return intent;
	}

	private Intent computeSharingIntent(JobContext jc, int maxItems) {
		ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true,
				maxItems);
		if (expandedPaths == null || expandedPaths.size() == 0) {
			setNfcBeamPushUris(null);
			return new Intent();
		}
		final ArrayList<Uri> uris = new ArrayList<Uri>();
		DataManager manager = mActivity.getDataManager();
		int type = 0;
		final Intent intent = new Intent();
		for (Path path : expandedPaths) {
			if (jc.isCancelled())
				return null;
			int support = manager.getSupportedOperations(path);
			type |= manager.getMediaType(path);

			if ((support & MediaObject.SUPPORT_SHARE) != 0) {
				uris.add(manager.getContentUri(path));
			}
		}

		final int size = uris.size();
		if (size > 0) {
			final String mimeType = MenuExecutor.getMimeType(type);
			if (size > 1) {
				intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType);
				intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
			} else {
				intent.setAction(Intent.ACTION_SEND).setType(mimeType);
				intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
			}
			intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
			setNfcBeamPushUris(uris.toArray(new Uri[uris.size()]));
		} else {
			setNfcBeamPushUris(null);
		}

		return intent;
	}

	public void updateSupportedOperation(Path path, boolean selected) {
		// TODO: We need to improve the performance
		updateSupportedOperation();
	}

	public void updateSupportedOperation() {
		// Interrupt previous unfinished task, mMenuTask is only accessed in
		// main thread
		if (mMenuTask != null)
			mMenuTask.cancel();

		updateSelectionMenu();

		// Disable share actions until share intent is in good shape
		if (mSharePanoramaMenuItem != null)
			mSharePanoramaMenuItem.setEnabled(false);
		if (mShareMenuItem != null)
			mShareMenuItem.setEnabled(false);

		// Generate sharing intent and update supported operations in the
		// background
		// The task can take a long time and be canceled in the mean time.
		mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
			@Override
			public Void run(final JobContext jc) {
				// Pass1: Deal with unexpanded media object list for menu
				// operation.
				ArrayList<MediaObject> selected = getSelectedMediaObjects(jc);
				if (selected == null) {
					mMainHandler.post(new Runnable() {
						@Override
						public void run() {
							mMenuTask = null;
							if (jc.isCancelled())
								return;
							// Disable all the operations when no item is
							// selected
							MenuExecutor.updateMenuOperation(mMenu, 0);
						}
					});
					return null;
				}
				final int operation = computeMenuOptions(selected);
				if (jc.isCancelled()) {
					return null;
				}
				int numSelected = selected.size();
				final boolean canSharePanoramas = numSelected < MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT;
				final boolean canShare = numSelected < MAX_SELECTED_ITEMS_FOR_SHARE_INTENT;

				final GetAllPanoramaSupports supportCallback = canSharePanoramas ? new GetAllPanoramaSupports(
						selected, jc) : null;

				// Pass2: Deal with expanded media object list for sharing
				// operation.
				final Intent share_panorama_intent = canSharePanoramas ? computePanoramaSharingIntent(
						jc, MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT)
						: new Intent();
				final Intent share_intent = canShare ? computeSharingIntent(jc,
						MAX_SELECTED_ITEMS_FOR_SHARE_INTENT) : new Intent();

				if (canSharePanoramas) {
					supportCallback.waitForPanoramaSupport();
				}
				if (jc.isCancelled()) {
					return null;
				}
				mMainHandler.post(new Runnable() {
					@Override
					public void run() {
						mMenuTask = null;
						if (jc.isCancelled())
							return;
						MenuExecutor.updateMenuOperation(mMenu, operation);
						MenuExecutor.updateMenuForPanorama(mMenu,
								canSharePanoramas
										&& supportCallback.mAllPanorama360,
								canSharePanoramas
										&& supportCallback.mHasPanorama360);
						if (mSharePanoramaMenuItem != null) {
							mSharePanoramaMenuItem.setEnabled(true);
							if (canSharePanoramas
									&& supportCallback.mAllPanorama360) {
								mShareMenuItem
										.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
								mShareMenuItem.setTitle(mActivity
										.getResources().getString(
												R.string.share_as_photo));
							} else {
								mSharePanoramaMenuItem.setVisible(false);
								mShareMenuItem
										.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
								mShareMenuItem.setTitle(mActivity
										.getResources().getString(
												R.string.share));
							}
							mSharePanoramaActionProvider
									.setShareIntent(share_panorama_intent);
						}
						if (mShareMenuItem != null) {
							mShareMenuItem.setEnabled(canShare);
							mShareActionProvider.setShareIntent(share_intent);
						}
					}
				});
				return null;
			}
		});
	}

	public void pause() {
		if (mMenuTask != null) {
			mMenuTask.cancel();
			mMenuTask = null;
		}
		mMenuExecutor.pause();
	}

	public void destroy() {
		mMenuExecutor.destroy();
	}

	public void resume() {
		if (mSelectionManager.inSelectionMode())
			updateSupportedOperation();
		mMenuExecutor.resume();
	}
}
